springboot @Configuration类型的class需要知道的细节

知识点收获

通过本文,你能收获什么?

  1. @Configuration的.class文件如何被spring加载的?
  2. @Configuration的.class文件如何被转化为BeanDefinition放入spring容器的?
  3. @Configuration的BeanDefinition如何转化为ConfigurationClass对象的
  4. ConfigurationClass对象属性在哪里开始被解析的?
  5. @Configuration的BeanDefinition的beanClass值何时变成proxy代理类的,为什么要变
  6. @Configuration类最终会生成cglib proxy代理类,这个由@Configuration类生成的cglib proxy代理类如何实例化的
  7. 我们能从中得到的扩展点有哪些

》本文力求专注和精简,希望你有所收获和想法

20191129002132.png

@Configuration注解作用

@Configuration标识的类有这些特性:可以声明多个@Bean方法,且在运行时被spring容器处理来生成BeanDefinition。@Configuration类是被AnnotationConfigWebApplicationContext启动(bootstrap)处理流程的。声明方式如下代码

1
2
3
4
5
6
7
 @Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
// instantiate, configure and return bean ...
}
}

@Configuration class存在如下约束,如下图
20191125072356.png

@Configuration class源码加载分析

@Configuration相关的类

如果IDE你使用的Idea,double Shift键,输入ConfigurationClass,会出现一长列的类,如下图
20191125075119.png
这些类分工协作,从加载标识@Configuration的.class文件,到生成BeanDefinition,再到生成ConfigurationClass对象,再到解析ConfigurationClass对象,即解析@ConfigurationClass class类中@Bean、@Import、@ImportResource等注解
|class|作用|
| – | – |
| ConfigurationClass | 专门表示@Configuration class。存储解析@Configuration class的信息 |
| ConfigurationClassPostProcessor | 专门用于处理@Configuration class的BeanDefinitoinRegistryPostProcessor(BeanFactoryPostProcessor子类) |
| AnnotationConfigUtils | 专门注册BeanPostProcessor和BeanFactoryPostProcessor到spring容器 |

@Configuration解析过程

ConfigurationClassPostProcessor实例创建过程

我们刚说过,ConfigurationClassPostProcessor是处理@Configuration class的核心组件,它是BeanFactoryPostProcessor类型子类且是BeanDefinitoinRegistryPostProcessor类型子类。BeanFactoryPostProcessor是AbstractApplicationContext's post-processor handling技术的规范接口,在项目启动较早时段,它便开始工作。或者说任何spring boot项目启动时都是走post-processor handling处理逻辑,这个逻辑的入口在PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()方法,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
PostProcessorRegistrationDelegate class
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
···
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); // (1)
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); // (2)
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
// 将@Configuration类生成BeanDefinition,此时的BeanDefinition.beanClass为原类class对象
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); // (3)

// Now, invoke the postProcessBeanFactory callback of all processors handled so far.
// 将(3)处的BeanDefinition.beanClass赋值为proxy代理类
nvokeBeanFactoryPostProcessors(registryProcessors, beanFactory);(4)
}
···
}

这个方法处理所有的BeanFactoryPostProcessor类型对象,但是通过优先级来规定先后处理顺序,优先级维度有两个:1:BeanDefinitionRegistryPostProcessor类型和BeanFactoryPostProcessor类型;2:PriorityOrdered和Ordered。而
ConfigurationClassPostProcessor同时实现了BeanDefinitionRegistryPostProcessor和PriorityOrdered,所以它得到最优先处理的机会

如上方法(1)处,从spring容器获取BeanDefinitionRegistryPostProcessor类型的beanName, 这个时期只有ConfigurationClassPostProcessor的beanName满足,需要指出:ConfigurationClassPostProcessor是通过硬编码的方式注册到spring容器的,详见AnnotationConfigUtils类),接着执行如上方法(2)处,以beanName为key从spring容器中获取ConfigurationClassPostProcessor实例。

应用ConfigurationClassPostProcessor实例加载@Configuration进行BeanDefinition注册

如上方法(3)处,即为应用ConfigurationClassPostProcessor实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
PostProcessorRegistrationDelegate class
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
}

ConfigurationClassPostProcessor class
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
processConfigBeanDefinitions(registry);
}

从方法名大概就知道方法的作用了,会调用所有BeanDefinitionRegistryPostProcessor类型postProcessBeanDefinitionRegistry()方法,此处只有ConfigurationClassPostProcessor符合。

应用ConfigurationClassPostProcessor实例将BeanDefinition.beanClass生成proxy代理类

如上invokeBeanFactoryPostProcessors方法(4)处,经过了方法(3)的处理,@Configuration类已经生成BeanDefinition,此时BeanDefinition.beanClass值为原类class对象。而方法(4)处目的是将BeanDefinition.beanClass赋值为proxy代理类,这里留个问题,其他为什么要设置成proxy代理类呢

关于方法(3)和方法(4)要做的事,参考下图,时机和作用清晰的对比和展示
20191212080200.png

@Configuration class过程解析

下面的方法就是解析@Configuration class的核心逻辑了。解析过程可以总结分三步,正好对应着方法中(1),(2),(3)处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
ConfigurationClassPostProcessor class
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();

for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)){
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));//(1)
}
}
··· 排序等
s
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);

Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates); //(2)
parser.validate();

Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());

// Read the model and create bean definitions based on its content
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());

this.reader.loadBeanDefinitions(configClasses); //(3)

}
while (!candidates.isEmpty());

··· ···
}

step1: 获取候选者
从spring容器拿到所有的beanDefinitionNames,然后遍历验证获得候选者,验证的依据是class metadata是否含有@Configuration注解,从下面我们可知,此时,beanDefinitionNames中只有consumerFeignApp符合条件。所以候选者就是consumerFeignApp及他的beanDefinition
20191126082030.png

step2: 通过候选者获取ConfigurationClass
找到了候选者,下面就对候选者进行解析,解析的全部功能和逻辑都集中在ConfigurationClassParser类中,看名称可知,这个类专业解析@Configuration类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
ConfigurationClassParser class
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
...
}

this.deferredImportSelectorHandler.process();
}

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// 评估标识@Configuration的类是否满足条件去加载,这是条件注解@ConditionalXXX起的作用
// 实际开发中,我们可以依据这个功能实现灵活的加载配置(如让谁加载进来,不让谁加载进来^_^)
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}

// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);

// 所有加载的@Configuration类都会转为ConfigurationClass放入这个map中
this.configurationClasses.put(configClass, configClass);
}

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass){
// 为了集中说明意图,隐藏了代码
// Recursively process any member (nested) classes first
// Process any @PropertySource annotations
// Process any @ComponentScan annotations
for (AnnotationAttributes componentScan : componentScans) {
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
if () {
// 进入递归调用
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}

// Process any @Import annotations
// Process any @ImportResource annotations
// Process individual @Bean methods
// Process default methods on interfaces
// Process superclass, if any
// No superclass -> processing is complete
return null;
}

如上方法整体的逻辑为对ConfigurationClass和SourceClass解析,检查他们有没有@ComponentScan,@Import,@Bean methods,@ImportResource,@PropertySource这些注解,如果有,分别对其解析,解析后的结果放入ConfigurationClass的各属性中,如下图
20191128000838.png

各个注解的属性值中可能又包含@Configuration注解,又要对包含的@Configuration注解进行解析,这样形成了递归,所以解析过程中有三个方法形成了三角递归调用的逻辑,如下图
20191127235053.png

这一步会将我们项目中定义的@Configuration类都加载进来,你可能有疑问,难道项目中我们自己定义的@Configuration类都是靠递归加载进来的?答案当然是NO,请注意@ComponentScan注解,这个注解的解析器很厉害,它把所有的标识@Component注解的class加载进来,而@Configuration,@RestController,@Service,@Repository等都包含@Component,所有这些注解的class都会加载进来形成BeanDefinition存入spring 容器(解析过程详见ComponentScanAnnotationParser)。说回来,对于@ComponentScan解析器加载进来的BeanDefinitoin,会进行时@Configuration进行过滤,从而得到@Configuration类,再次调用parse()方法,这时体现出三角递归调用了。此时,项目中所有我们自定义的@Configuration类都获取到了

step3: 解析每个ConfigurationClass
step2中对@Configuration类的@Import,@Bean methods,@ImportResource进行解析,解析的结果放入ConfigurationClass对象的importBeanDefinitionRegistrars,beanMethods,importedResources,metadata等属性。
所以,step2将@Configuration类的解析结果都放入了ConfigurationClass对象,即ConfigurationClass对象包装了@Configuration类的所有信息。

回到ConfigurationClassPostProcessor.processConfigBeanDefinitions()方法(3)处,现在,我们解析ConfigurationClass,而解析ConfigurationClass过程由ConfigurationClassBeanDefinitionReader类负责的
看code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ConfigurationClassBeanDefinitionReader class
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}

private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}

if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}

loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

看loadBeanDefinitionsForConfigurationClass()方法,方法主要功能为对ConfigurationClass的beanMethods,importedResources,importBeanDefinitionReistrars属性进行解析,为什么要对这三个属性进行解析呢,看看这三个其@Import,@Bean methods,@ImportResource的用法

1
2
3
4
5
6
7
8
9
@Import(DispatcherServletConfiguration.class)
@ImportResource("classpath:/com/acme/database-config.xml")
protected static class DispatcherServletRegistrationConfiguration {

@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(){
...
}
}

可以看到,这三个注解的属性值都是类或者配置文件或者加载文件的类,所以,需要解析,从而将解析到的.class文件转化为BeanDefinition放入spring容器。

@Configuration类的cglib代理类实例化分析

由于@Configuration注解的都是类,而非接口,所有这里使用的是cglib代理技术,ConfigurationClassEnhancer包装了cglib。这里我们实际工作中可以直接复用ConfigurationClassEnhancer满足我们生成代理类的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
实现自BeanFactoryPostProcessor.postProcessBeanFactory
ConfigurationClassPostProcessor class
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 生成代理
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

ConfigurationClassPostProcessor.ConfigurationClassEnhancer class
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
if (!(beanDef instanceof AbstractBeanDefinition)) {
throw
}
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}

ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// If a @Configuration class gets proxied, always proxy the target class
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// Set enhanced subclass of the user-specified bean class
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
if (configClass != null) {
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
beanDef.setBeanClass(enhancedClass);
}
}
}
}

ConfigurationClassPostProcessor.ConfigurationClassEnhancer class
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
return configClass;
}
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
return enhancedClass;
}

代理的代码很清晰,很值得我们学习

it`s time to summary

整个过程可以看做是一颗小树长成参天大树,consumerFeignApp就是树苗,而我们项目的代码就是后来大树的枝干和叶子。枝干和叶子与@ComponentScan,@Import,@Bean methods,@ImportResource,@PropertySource,@Configuration交织在一起被解析出来,生成beanDefinition、实例对象或代理类。

本文主要说明了标识了@Configuration的.class文件,是如何被解析成ConfigurationClass,再到转化为ConfigurationClassBeanDefinition放入spring容器,再到如何解析ConfigurationClass对象属性。为了集中阐述@Configuration,所以,其他的部分这里不做详述和延展。如果阅读后你有所收获,共享欢喜

知识点收获回复

1,2,3,4相信你已经有答案了,现在,我们看看扩展点有什么

  1. 加载一些package目录下的.class文件
    1
    2
    3
    4
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
    componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(StringUtils.toStringArray(basePackages));
    如此两行代码就实现了加载package目录下的.class文件的功能。详见ComponentScanAnnotationParser.parse()方法